package com.zjq.smartdevice.service.impl;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.zjq.smartdevice.cache.CategoryCache;
import com.zjq.smartdevice.cache.SceneCache;
import com.zjq.smartdevice.enums.CheckType;
import com.zjq.smartdevice.events.ColorCommandEvent;
import com.zjq.smartdevice.events.PowerCommandEvent;
import com.zjq.smartdevice.exception.AddDataErrorException;
import com.zjq.smartdevice.exception.MyErrorCode;
import com.zjq.smartdevice.exception.QueryDataErrorException;
import com.zjq.smartdevice.mapper.SysDeviceMapper;
import com.zjq.smartdevice.pojo.SysCategory;
import com.zjq.smartdevice.pojo.SysDevice;
import com.zjq.smartdevice.pojo.SysScene;
import com.zjq.smartdevice.pojo.SysUserInfo;
import com.zjq.smartdevice.service.SysDeviceService;
import com.zjq.smartdevice.utils.SecurityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;

import java.util.Date;
import java.util.List;
import java.util.Map;

/**
 * @User zhangjiaqi
 * @Project project
 * @Package com.zjq.smartdevice.service.impl
 * @Date 2022/3/23 20:44
 */
@Service
@Transactional
public class SysDeviceServiceImpl implements SysDeviceService {

    private ApplicationContext context;

    @Autowired
    public void setContext(ApplicationContext context) {
        this.context = context;
    }

    private ObjectMapper objectMapper;

    @Autowired
    public void setObjectMapper(ObjectMapper objectMapper) {
        this.objectMapper = objectMapper;
    }

    private CategoryCache categoryCache;

    @Autowired
    public void setCategoryCache(CategoryCache categoryCache) {
        this.categoryCache = categoryCache;
    }

    private SceneCache sceneCache;

    @Autowired
    public void setSceneCache(SceneCache sceneCache) {
        this.sceneCache = sceneCache;
    }

    private SysDeviceMapper sysDeviceMapper;

    @Autowired
    public void setSysDeviceMapper(SysDeviceMapper sysDeviceMapper) {
        this.sysDeviceMapper = sysDeviceMapper;
    }

    @Override
    public void addSysDevice(SysDevice device) {
        //需要对数据进行校验
        Assert.isTrue(device.isEmpty(CheckType.ADD),()->{
            throw new AddDataErrorException("数据不能为空", MyErrorCode.DATA_NULL);
        });
        //判断用户有没有登录
        //必须登录
        //检查当前是不是登录了用户,如何判断用户登录了, 所以一定有个地方保存了登录的用户(数据到底保存在什么地方是我们决定的),我们登录完成后放到session了
        //那我们只需要从session中获取
        SysUserInfo loginUser = SecurityUtils.getLoginUser();
        //我们要求必须登录
        Assert.notNull(loginUser,()->{
            throw new AddDataErrorException("未登陆", MyErrorCode.NOT_LOGIN);
        });
        device.setBindUserId(loginUser.getUId());//这个登录用户一定要有值的
        //检查设备是不是已经被绑定
        SysDevice sysDevice = sysDeviceMapper.findById(device.getDeviceId());
        Assert.isNull(sysDevice,()->{
            throw new AddDataErrorException("设备已经被绑定", MyErrorCode.DEVICE_ALLREADY_BIND);
        });
        //检查场景是不是空的,是空的就设置为-1.不是空的就检查这个场景属于不属于当前用户,因为只能添加到自己的账户上
        if (device.getSceneId() == null) {
            device.setSceneId(-1L);
        } else if (device.getSceneId() != -1) {
            //传递场景id,但是不是-1,需要检查这个场景的id是不是属于当前用户
            //我们有场景的缓存,可以直接从缓存中查询这个场景的数据
            SysScene sysScene = sceneCache.get(device.getSceneId());
            //这个场景可能不存在,也可能不属于当前用户,但是不管什么情况,只要不符合,我们就提示场景不存在
            Assert.isTrue(sysScene!=null&&sysScene.getCreateBy()==loginUser.getUId(),()->{
                throw new AddDataErrorException("场景不存在", MyErrorCode.SCENE_NOT_EXIST);
            });
        }
        //检查分类是不是存在,分类首先必须存在,但是可以状态为0,被删除状态,这是我们的业务要求,我们当前项目是允许添加被删除的分类设备
        SysCategory category = categoryCache.getWithDelete(device.getCategyId());
        Assert.notNull(category,()->{
            throw new AddDataErrorException("分类不存在", MyErrorCode.CATEGORY_NOT_EXIST);
        });
        device.setBindTime(new Date());//设置绑定时间
        sysDeviceMapper.addSysDevice(device);

    }

    @Override
    public SysDevice findById(String deviceId) {

        //判断用户有没有登录
        SysUserInfo loginUser = SecurityUtils.getLoginUser();
        //我们要求必须登录
        Assert.notNull(loginUser,()->{
            throw new QueryDataErrorException("未登陆", MyErrorCode.NOT_LOGIN);
        });

        SysDevice sysDevice = sysDeviceMapper.findById(deviceId);
        //判断是不是当前用户绑定的
        Assert.isTrue(sysDevice == null,()->{
            throw new QueryDataErrorException("设备不存在", MyErrorCode.DEVICE_NOT_EXIST);
        });
        return sysDevice;
    }

    @Override
    public int updateDeviceById(SysDevice sysDevice) {
        //校验数据
        int result = sysDeviceMapper.updateDeviceById(sysDevice);
        return result;
    }

    @Override
    public PageInfo<SysDevice> findByPage(int page, int limit, SysDevice sysDevice) {
        PageHelper.startPage(page, limit);
        List<SysDevice> deviceList = sysDeviceMapper.findAll(sysDevice);
        //当前设备关联的分类数据没有查询
        deviceList.forEach(sysDevice1 -> {
            Long categyId = sysDevice1.getCategyId();//当前设备关联的分类id
            //我们有一个分类缓存,分类缓存中提供了一个根据id查询分类的功能
            SysCategory category = categoryCache.getWithDelete(categyId);
            sysDevice1.setCategory(category);//给当前的设备设置分类
        });
        PageInfo<SysDevice> pageInfo = new PageInfo<>(deviceList);
        return pageInfo;
    }

    @Override
    public void sendControl(String devideId, String command) {
        //发送命令是给指定的设备
        //我们得先找到要发送的命令是什么
        //命令在设备的分类中,分类的id在设备的数据中
        //我们现在知道一个设备的id,通过设备的id查询到分类的id,然后根据分类的id查询到命令,解析命令,然后根据传递的command找到真正的命令
        SysDevice device = sysDeviceMapper.findById(devideId);
        Assert.notNull(device, () -> {
            throw new QueryDataErrorException("设备不存在", MyErrorCode.DEVICE_NOT_EXIST);
        });
        Long categyId = device.getCategyId();//分类id
        SysCategory category = categoryCache.getWithDelete(categyId);//找到分类
        String txCommand = category.getTxCommand();//获取到对应的命令
        Assert.hasText(txCommand,()->{
            throw new QueryDataErrorException("操作不存在", MyErrorCode.PERMSSION_NOT_ALLOWED);
        });
        try {
            Map map = objectMapper.readValue(txCommand, Map.class);//解析命令
            Object o = map.get(command);//获取当前操作对应的命令
            if (o instanceof Map) {
                int type = (int) ((Map) o).get("type");//命令的类型,1是给设备发送,2是给对应的颜色控制器发送
                String realCommand = (String) ((Map<?, ?>) o).get(command);
                switch (type) {
                    case 1:
                        //给设备发送map值command值出去,设备是要通过websocket进行发送命令的
                        context.publishEvent(new PowerCommandEvent(devideId, realCommand));
                        break;
                    case 2:
                        //给颜色控制器发送命令
                        context.publishEvent(new ColorCommandEvent(devideId, realCommand));
                        break;
                }
            }

        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }

    }
}
